#region Copyright
// 
// Copyright (C) 2008 VirtualStaticVoid <virtualstaticvoid@gmail.com>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
#endregion

using System;
using System.Linq;

namespace NAom.Core
{
  public class DynamicType<TTarget> : IDynamicType
      where TTarget : class
  {

    public DynamicType()
      : this(typeof(TTarget).Name)
    {
    }

    public DynamicType(string name)
      : this(name, typeof(TTarget))
    {
    }

    public DynamicType(string name, params IPropertyType[] propertyTypes)
      : this(name, typeof(TTarget), propertyTypes)
    {
    }

    public DynamicType(string name, Type baseType, params IPropertyType[] propertyTypes)
    {
      if (name == null) throw new ArgumentNullException("name");
      if (String.IsNullOrEmpty(name)) throw new ArgumentException(SR.InvalidDynamicTypeName, "name");
      ValidateTargetType(baseType);

      Name = name;
      BaseType = baseType;
      Properties = new PropertyTypeCollection();
      SupportedInterfaces = new InterfaceCollection();
      IncludePropertyChangeNotifications = false;

      // if the TTarget type isn't the same as the base type, and TTarget is an interface
      if (typeof(TTarget) != baseType && typeof(TTarget).IsInterface)
        SupportedInterfaces.Add(typeof(TTarget));

      Properties.AddRange(propertyTypes);

    }

    #region IDynamicType Members

    public string Name { get; private set; }

    public Type BaseType { get; private set; }

    public PropertyTypeCollection Properties { get; private set; }

    public InterfaceCollection SupportedInterfaces { get; private set; }

    public bool IncludePropertyChangeNotifications { get; set; }

    IDynamicTypeFactory IDynamicType.CreateFactory()
    {
      return this.CreateFactory();
    }

    #endregion

    // TODO: cache created factory
    //  assert Properties, SupportedInterfaces and IncludePropertyChangeNotifications are unchanged
    // is this a requirement?
 
    public IDynamicTypeFactory<TTarget> CreateFactory()
    {
      return CreateFactory(new DynamicTypeGenerator());
    }

    public IDynamicTypeFactory<TTarget> CreateFactory(IDynamicTypeGenerator typeGenerator)
    {
      if (typeGenerator == null) throw new ArgumentNullException("typeGenerator");

      ValidatePropertyTypes(Properties);
      Type generatedType = typeGenerator.GenerateType(this, BaseType, typeof(TTarget), SupportedInterfaces.ToArray(), IncludePropertyChangeNotifications);
      return new DynamicTypeFactory<TTarget>(this, generatedType, Properties.AsReadOnly());
    }

    private static void ValidateTargetType(Type targetType)
    {
      if (targetType == null) throw new ArgumentNullException("targetType");
      if (!targetType.IsVisible) throw new NAomException(SR.PrivateTypeOrInterface(targetType));
      if (targetType.IsSealed) throw new NAomException(SR.SealedTypeCannotBeImplemented(targetType));
      if (targetType.IsGenericType) throw new NAomException(SR.GenericTypeCannotBeImplemented(targetType));

      // TODO: if abstract type, check for abstract members

    }

    private static void ValidatePropertyTypes(PropertyTypeCollection propertyTypes)
    {
      if (propertyTypes == null) throw new ArgumentNullException("propertyTypes");

      foreach (IPropertyType propertyType in propertyTypes)
      {

        if (!propertyType.DataType.IsVisible) throw new NAomException(SR.PrivateTypeOrInterface(propertyType.DataType));

        // must implement IInternalPropertyType 
        //  since IInternalPropertyType is internal then it can be assumed 
        //   that it derives from PropertyType<T>
        IInternalPropertyType internalPropertyType = propertyType as IInternalPropertyType;
        if (internalPropertyType == null)
          throw new NAomException(SR.UnsupportedPropertyTypeImplementation(propertyType.GetType(), typeof(IPropertyType), typeof(PropertyType<>)));

        // bound already?
        if (internalPropertyType.IsPropertyAccessorBound)
          throw new NAomException(SR.PropertyTypeAlreadyBound(propertyType.Name));

      }
    }

  }
}
